﻿Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Reflection
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Web.Services
Imports System.Web.Services.Description
Imports System.Web.Services.Discovery
Imports System.Xml

Class KelDynamicWS
    Private availableTypes As Dictionary(Of String, Type)

    ''' <summary>
    ''' Liste des services disponibles dans ce service Web.
    ''' </summary>
    Public ReadOnly Property AvailableServices As List(Of String)
        Get
            Return Me.services
        End Get
    End Property

    ''' <summary>
    ''' Crée l'invocateur de service utilisant le service Web indiqué.
    ''' </summary>
    ''' <param name="webServiceUri">Uri du service</param>
    Public Sub New(ByVal webServiceUri As Uri)
        MyBase.New()
        Me.services = New List(Of String)
        ' available services
        Me.availableTypes = New Dictionary(Of String, Type)
        ' available types
        ' create an assembly from the web service description
        Me.webServiceAssembly = BuildAssemblyFromWSDL(webServiceUri)
        ' see what service types are available
        Dim types() As Type = Me.webServiceAssembly.GetExportedTypes
        ' and save them
        For Each type As Type In types
            services.Add(type.FullName)
            availableTypes.Add(type.FullName, type)
        Next
    End Sub

    ''' <summary>
    ''' Obtient une liste de toutes les méthodes disponibles pour le service indiqué.
    ''' </summary>
    ''' <param name="serviceName">Le nom du service pour l'utiliser.</param>
    ''' <returns></returns>
    Public Function EnumerateServiceMethods(ByVal serviceName As String) As List(Of String)
        Dim methods As List(Of String) = New List(Of String)
        If Not Me.availableTypes.ContainsKey(serviceName) Then
            Throw New Exception("Service Not Available")
        Else
            Dim type As Type = Me.availableTypes(serviceName)
            ' only find methods of this object type (the one we generated)
            ' we don't want inherited members (this type inherited from SoapHttpClientProtocol)
            For Each minfo As MethodInfo In type.GetMethods((BindingFlags.Instance _
                            Or (BindingFlags.Public Or BindingFlags.DeclaredOnly)))
                methods.Add(minfo.Name)
            Next
            Return methods
        End If
    End Function

    ''' <summary>
    ''' Invoque la méthode indiquée du service nommé.
    ''' </summary>
    ''' <param name="serviceName">Le nom du service pour l'utiliser.</param>
    ''' <param name="methodName">Le nom de la méthode à appeler.</param>
    ''' <param name="args">Les arguments à passer à la méthode.</param>
    ''' <returns>La valeur de retour de la méthode du service Web.</returns>
    Public Function InvokeMethod(ByVal serviceName As String, ByVal methodName As String, ByVal ParamArray args() As Object) As Object
        ' create an instance of the specified service
        ' and invoke the method
        Dim obj As Object = Me.webServiceAssembly.CreateInstance(serviceName)
        Dim type As Type = obj.GetType
        Return CType(type.InvokeMember(methodName, BindingFlags.InvokeMethod, Nothing, obj, args), Object)
    End Function

    ''' <summary>
    ''' Construit l'importateur de description de service Web, qui nous permet de produire une classe de proxy
    ''' basée sur le contenu du WSDL décrit par le XmlTextReader.
    ''' </summary>
    ''' <param name="xmlreader">Le contenu du WSDL, décrit en XML.</param>
    ''' <returns>Un ServiceDescriptionImporter qui peut être utilisé pour créer une classe de proxy.</returns>
    Private Function BuildServiceDescriptionImporter(ByVal xmlreader As XmlTextReader) As ServiceDescriptionImporter
        ' make sure xml describes a valid wsdl
        If Not serviceDescription.CanRead(xmlreader) Then
            Throw New Exception("Invalid Web Service Description")
        End If
        Dim servDescription As ServiceDescription
        servDescription = ServiceDescription.Read(xmlreader)
        ' build an importer, that assumes the SOAP protocol, client binding, and generates properties
        Dim descriptionImporter As ServiceDescriptionImporter = New ServiceDescriptionImporter
        'descriptionImporter.ProtocolName = "Soap";
        descriptionImporter.AddServiceDescription(servDescription, Nothing, Nothing)
        descriptionImporter.Style = ServiceDescriptionImportStyle.Client
        descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties
        Return descriptionImporter
    End Function

    ''' <summary>
    ''' Compile une Assembly de la classe de proxy fournie par le ServiceDescriptionImporter.
    ''' </summary>
    ''' <param name="descriptionImporter"></param>
    ''' <returns>Une Assembly qui peut être utilisée pour exécuter les méthodes de service Web.</returns>
    Private Function CompileAssembly(ByVal descriptionImporter As ServiceDescriptionImporter) As Assembly
        ' a namespace and compile unit are needed by importer
        Dim codeNamespace As CodeNamespace = New CodeNamespace
        Dim codeUnit As CodeCompileUnit = New CodeCompileUnit
        codeUnit.Namespaces.Add(codeNamespace)
        Dim importWarnings As ServiceDescriptionImportWarnings = descriptionImporter.Import(codeNamespace, codeUnit)
        If (importWarnings = 0) Then
            ' create a c# compiler
            Dim compiler As CodeDomProvider = CodeDomProvider.CreateProvider("VisualBasic")
            ' include the assembly references needed to compile
            Dim references() As String = New String() {"System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll"}
            Dim parameters As CompilerParameters = New CompilerParameters(references)
            ' compile into assembly
            Dim results As CompilerResults = compiler.CompileAssemblyFromDom(parameters, codeUnit)
            For Each oops As CompilerError In results.Errors
                ' trap these errors and make them available to exception object
                Throw New Exception(("Compilation Error Creating Assembly : " + oops.ToString))
            Next
            ' all done....
            Return results.CompiledAssembly
        Else
            ' warnings issued from importers, something wrong with WSDL
            Throw New Exception("Invalid WSDL")
        End If
    End Function

    ''' <summary>
    ''' Construit une Assembly lié à une description de service Web.
    ''' L'Assembly peut être utilisé pour exécuter les méthodes du service Web
    ''' </summary>
    ''' <param name="webServiceUri">Emplacement du WSDL.</param>
    ''' <returns>Une Assembly de service Web.</returns>
    Private Function BuildAssemblyFromWSDL(ByVal webServiceUri As Uri) As Assembly
        If String.IsNullOrEmpty(webServiceUri.ToString) Then
            Throw New Exception("Web Service Not Found")
        End If
        Dim xmlreader As XmlTextReader = New XmlTextReader((webServiceUri.ToString + "?wsdl"))
        Dim descriptionImporter As ServiceDescriptionImporter = BuildServiceDescriptionImporter(xmlreader)
        Return CompileAssembly(descriptionImporter)
    End Function

    Private webServiceAssembly As Assembly

    Private services As List(Of String)
End Class